/*
 * Luabase.as
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package bsp.protocol
{
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import bsp.utils.Debug;
	
	public class Luabase
	{
		private const PKG_VALUE_TYPE_BYTE			: uint = 0x1;
		private const PKG_VALUE_TYPE_SHORT			: uint = 0x2;
		private const PKG_VALUE_TYPE_INT			: uint = 0x3;
		private const PKG_VALUE_TYPE_LONGLONG		: uint = 0x4;
		private const PKG_VALUE_TYPE_FLOAT			: uint = 0x5;
		private const PKG_VALUE_TYPE_STRING			: uint = 0x6;
		private const PKG_VALUE_TYPE_ARRAY_START	: uint = 0xF0;
		private const PKG_VALUE_TYPE_ARRAY_END		: uint = 0xF1;
		private const PKG_VALUE_TYPE_NULL			: uint = 0xFF;
		
		private const PKG_UNSUPPORTED_TYPE_KEY		: String = "__UNSUPPORTED_DATA_TYPE_KEY__";
		
		public function Luabase()
		{
			return;
		}
		
		// Hook : pack a command stream
		public function hsend(cmd_id : uint, params : Object) : ByteArray
		{
			var rtn : ByteArray = new ByteArray();
			var p : ByteArray = this._pack(params);
			
			rtn.endian = Endian.BIG_ENDIAN;
			rtn.writeUnsignedInt(cmd_id);
			rtn.writeUnsignedInt(p.length);
			rtn.writeBytes(p);
			
			return rtn;
		}
		
		// Hook : Unpack a stream into original object
		public function hrecv(input : ByteArray) : Object
		{
			var rtn : Object = new Object();
			
			if (input.bytesAvailable < 8)
			{
				return null;
			}
			
			rtn.cmd_id = input.readUnsignedInt();
			rtn.cmd_len = input.readUnsignedInt();
			
			if (input.bytesAvailable < rtn.cmd_len)
			{
				return null;
			}
			
			var p : ByteArray = new ByteArray();
			input.readBytes(p, 0, rtn.cmd_len);
			rtn.params = this._unpack(p);
			
			return rtn;
		}
		
		// Luabase :: unpack
		private function _unpack(data : ByteArray) : Object
		{
			var r : Object = new Object();
			var remaining : uint = data.bytesAvailable;
			var vtype : uint;
			var vlen : uint;
			var vn : Number;
			var vih : uint;
			var vil : uint;
			var key : String = "";
			var is_key : Boolean = true;
			var curr_obj : Object = r;
			var curr_level : uint = 0;
			var levels : Array = new Array();
			var rec : Boolean;
			
			data.endian = Endian.BIG_ENDIAN;
			
			while (remaining > 0)
			{
				vtype = data.readUnsignedByte();
				rec = false;
				//trace("LOOP");
				//Debug.dump_object(r);
				
				if (is_key)
				{
					// All goes string
					switch (vtype)
					{
						case PKG_VALUE_TYPE_BYTE : 
							vn = data.readUnsignedByte();
							key = vn.toString();
							break;
						
						case PKG_VALUE_TYPE_SHORT : 
							vn = data.readUnsignedShort();
							key = vn.toString();
							break;
						
						case PKG_VALUE_TYPE_INT : 
							vn = data.readUnsignedInt();
							key = vn.toString();
							break;
						
						case PKG_VALUE_TYPE_LONGLONG : 
							vih = data.readUnsignedInt();
							vil = data.readUnsignedInt();
							vn = vih << 32 + vil;
							key = vn.toString();
							break;
						
						case PKG_VALUE_TYPE_FLOAT : 
							vn = data.readDouble();
							key = vn.toString();
							break;
						
						case PKG_VALUE_TYPE_STRING : 
							vlen = data.readUnsignedInt();
							key = data.readUTFBytes(vlen);
							break;
						
						case PKG_VALUE_TYPE_ARRAY_END : 
							if (curr_level < 1)
							{
								break;
							}
							
							curr_obj = levels[-- curr_level];
							rec = true;
							break;
						
						default : 
							// Unsupported data type
							key = this.PKG_UNSUPPORTED_TYPE_KEY;
							break;
					}
					
					if (!rec)
					{
						curr_obj[key] = void;
						is_key = false;
					}
					
					else
					{
						is_key = true;
						remaining = data.bytesAvailable;
						continue;
					}
				}
				
				else
				{
					switch (vtype)
					{
						case PKG_VALUE_TYPE_BYTE : 
							curr_obj[key] = data.readUnsignedByte();
							break;
						
						case PKG_VALUE_TYPE_SHORT : 
							curr_obj[key] = data.readUnsignedShort();
							break;
						
						case PKG_VALUE_TYPE_INT : 
							curr_obj[key] = data.readUnsignedInt();
							break;
						
						case PKG_VALUE_TYPE_LONGLONG : 
							vih = data.readUnsignedInt();
							vil = data.readUnsignedInt();
							curr_obj[key] = vih << 32 + vil;
							break;
						
						case PKG_VALUE_TYPE_FLOAT : 
							curr_obj[key] = data.readDouble();
							break;
						
						case PKG_VALUE_TYPE_STRING : 
							vlen = data.readUnsignedInt();
							curr_obj[key] = data.readUTFBytes(vlen);
							break;
						
						case PKG_VALUE_TYPE_ARRAY_START : 
							curr_obj[key] = new Object();
							levels[curr_level ++] = curr_obj;
							curr_obj = curr_obj[key];
							break;
						
						case PKG_VALUE_TYPE_NULL : 
							break;
						
						default : 
							// Unsupported type
							break;
					}
					
					is_key = true;
					//trace("Value", curr_obj[key]);
				}
				
				remaining = data.bytesAvailable;
			}
			
			return r;
		}
		
		// Luabase :: pack
		private function _pack(data : Object) : ByteArray
		{
			var r : ByteArray = new ByteArray();
			r.endian = Endian.BIG_ENDIAN;
			this._ins_stream(data, r);
			
			return r;
		}
		
		private function _ins_stream(o : Object, s : ByteArray) : void
		{
			for (var key : String in o)
			{
				var keyb : ByteArray = new ByteArray();
				
				// Write original key
				s.writeByte(PKG_VALUE_TYPE_STRING);
				keyb.writeUTFBytes(key);
				s.writeUnsignedInt(keyb.length);
				s.writeBytes(keyb);
				
				// Write value
				if (o[key] is uint)
				{
					var vi : uint = o[key] as uint;
					if (vi <= 0xFF)
					{
						s.writeByte(PKG_VALUE_TYPE_BYTE);
						s.writeByte(vi);
					}
					
					else if (vi <= 0xFFFF)
					{
						s.writeByte(PKG_VALUE_TYPE_SHORT);
						s.writeShort(vi);
					}
					
					else
					{
						s.writeByte(PKG_VALUE_TYPE_INT);
						s.writeUnsignedInt(vi);
					}
				}
				
				else if (o[key] is Number)
				{
					// Long long or double
					var vn : Number = o[key] as Number;
					var vns : String = vn.toString();
					if (vns.indexOf('.') >= 0)
					{
						// IEEE 754 type double number
						s.writeByte(PKG_VALUE_TYPE_FLOAT);
						s.writeDouble(vn);
					}
					
					else
					{
						// Unsigned long long
						s.writeByte(PKG_VALUE_TYPE_LONGLONG);
						s.writeUnsignedInt(vn >> 32);
						s.writeUnsignedInt(vn % 0x10000000);
					}
				}
				
				else if (o[key] is String)
				{
					var vs : String = o[key] as String;
					var vsb : ByteArray = new ByteArray();
					
					s.writeByte(PKG_VALUE_TYPE_STRING);
					vsb.writeUTFBytes(vs);
					s.writeUnsignedInt(vsb.length);
					//s.writeUnsignedInt(vs.length);	// Crashing.. wrong length
					s.writeBytes(vsb);
				}
				
				else if (o[key] is Object)
				{
					// Sub-array
					s.writeByte(PKG_VALUE_TYPE_ARRAY_START);
					_ins_stream(o[key], s);
					s.writeByte(PKG_VALUE_TYPE_ARRAY_END);
				}
				
				else
				{
					// Unsupported data type
				}
			}
			
			return;
		}
	}
}